<!--

前言

继前面几个章节的铺垫,我想读者或多或少对BLE MESH整个入网过程的了解,应该十成中起码占了有八成吧,剩下的2成通过反复阅读应该不是什么大问题;讲完了BLE MESH入网过程,那么接下来就是进行Model之间的数据交互了;这也是以后BLE MESH开发过程需要掌握的重要知识,试想一下这样一个场景:

我想没有人希望这样子吧?只有深刻地了解各个层的帧结构,当遇到这样或那样的问题时,我们通过抓包器分析对比就能很快锁定问题的源头,从而为我们快速解决问题打下坚实的基础,这也是本篇章节的主要目的。

PB-GATT && PB-ADV

小编在SIG MESH协议各个层的作用中已经讲过它们之间的区别:

然而,那只是从宏观的角度去说明它们之间的区别。而在该章节开始之前,小编觉得我们还应该从数据量化的角度去区分这两者在传输的数据格式和种类之间的不同。

PB-GATT

GATT承载说得直白一点就是说,通过Proxy Data In和Proxy Data Out进行数据的收与发,换句话说就是基于连接的方式进行数据交互,这里强调的是传递数据的载体;但是,具体收发的数据类型还是与PB-ADV有所不同的,这点我们在之前的章节也有所提及,具体为:

Network PDU

虽然Proxy Data In和Proxy Data Out允许进行数据交互的长度取决于ATT_MTU,但是该类型PDU的的帧长度则是基于广播承载来设计的,也就是说就算ATT_MTU的值大于31;此时,PB-GATT的设备仍然只能传递最大29字节的Network PDU,多出来的数据则放在下一个Network PDU发送出去。这一点很重要!!!

Mesh Beacon

Beacon类型的数据比较简单,帧格式相对比较固定。基本上一包数据可以一次性发送完成,不需要分包。

Proxy Configuration

因为该PDU的帧格式跟Network PDU是一样的;同样的,即使ATT_MTU的值大于31,最大也只能传输29个字节的数据,多的那一部分则放在下一个Proxy Configuration PDU发送出去。而且仅具备Proxy特性的设备才支持该类型PDU。

Provisioning PDU

该类型的PDU就不一样了,其完全可以挣脱开束缚。只要ATT_MTU的值够大,那么就可以发送多大的Provisioning PDU。例如:发送64字节的Public Key时,在ATT_MTU够大的情况下就可以一次性发送完成,而且不需要分包。而PB-ADV在处理这事的时候则必须分包。

PB-ADV

Mesh Message

实质就是Network PDU,只是换了一种叫法;该类型PDU最大的帧大小为29个字节,如果应用层的数据通过层层拼装到达Network层之后,其大小大于29字节则需要分包才能将所有的数据发送出去。

Mesh Beacon

PB-GATT-Mesh-Beacon

Generic Provisioning PDU

在帧格式这方面,基本上跟PB-GATT的是一样的。唯一不同的是PB-ADV在传递这些信息时,大部分情况下是分包才能完成整个数据包的传输,因为它走的是广播通道,最大只能传送31个字节。

希望读者不要被PB-GATT和PB-ADV迷糊了,它们都是用来进行数据收发的载体;前者走的是数据通道,有些数据包它可以根据ATT_MTU一次性传输完成,而后者走的是广播通道,一次性最大只能传递31字节的数据;只是PB-GATT更多的用于Proxy Client与Proxy Server之间基于连接的方式进行数据交互。

BLE Mesh Packet帧格式

在前面几个篇章,小编就一直再强调目前所有的BLE MESH Packets都是通过不可连接非定向类型的广播PDU传递信息到MESH网络中;此时,有些读者可能会有以下几个疑问:

至于上面提及到的疑问,有一个概念我们还需要再重复一遍:

“PB-GATT只是让那些不支持BLE MESH的设备,如手机、电脑等间接地加入到BLE MESH网络中;其次,那么这些不支持BLE MESH的设备想要在MESH网络中设置或者获取网络中其他节点的数据,应该将数据通过BLE GATT传送给Proxy节点(类似于中转站),最后再由Proxy节点将数据通过BLE广播包 (不可连接非定向广播) 传递到MESH网络中去处理; 但是,也有一个例外:那就是手机通过PB-GATT跟Proxy节点已经建立连接,而手机此时想要获取或者设置Proxy节点 (Proxy client<-->Proxy server),则仍然是使用GATT的方式进行数据交互;同时,这也说明Proxy节点是必须同时支持PB-GATT和PB-ADV”

比如:“手机的Mesh APP跟某一个Proxy Node建立了连接,现在其还需要控制另外一个节点,就可以用上述办法去实现”;这个时候读者可能会有这个的一个疑问“为什么不直接跟其进行连接并进行控制呢?”---如果这个时候还有几十个或者上百个节点要控制,那么这种方式是绝对行不通的。 为了让读者有更加深刻的了解,我们可以参考如下的图示:

  1. Provisioner想要获取D4:6A:02:F2:A2:B4这个MAC地址节点的状态,但是这个节点并没有跟Provisioner建立任何连接
  2. Provisioner通过数据通道将控制命令发送给与其连接的D3:31:5A:DB:35:91
  3. D3:31:5A:DB:35:91收到之后,就将接收到的控制命令通过不可连接非定向广播发送到Secure Mesh Network中去,然后D3:31:5A:DB:35:91通过广播通道收到D4:6A:02:F2:A2:B4响应的状态包之后,再通过数据通道将D4:6A:02:F2:A2:B4当前的状态告诉Provisioner

BLE Mesh Packet通过广播包的方式传递数据的最大长度就只有31字节,大于31字节的数据包就要进行分包处理了。那么,BLE MESH Specification是如何划分这31个字节的,这里小编不得再次将SIG MESH协议各个层的作用中提及到的MESH Packet示意图拿出来向广大读者展示。

那么我们如何来看上面的示意图呢?对于发送方,其只要将数据打包成Network PDU的帧格式就可以了,至于什么传输层、访问层的内容那是接收方接收到之后,通过层层解密最终上抛给应用层处理。

Network PDU

在开始分段式细述之前,我们先上一幅NetWork PDU的捉包图再来讲解:

我们可以看到,Network PDU的帧格式完全跟上面MESH Packet示意图所述的一模一样。其中,我们可以观察到除了IVI|NID和NetMIC,其他的字段 (CTL|TTL、SEQ、SRC) 要么被模糊化处理了,要么被NetKey加密 (DST、Transport PDU) 了。

IVI

该值相对来说比较好理解,其表示IV索引值的最低有效位。例如:IV索引值为0x00101847,则此时IVI为1;当0x00101846时,则此时IVI为0。至于其在整个Network PDU中也就起到这样的作用,除此之外并无他用。

NID

该字段的全称是 “Network Identifier”,即网络标识符的意思;该值由Network Key派生出来,其主要起到标识的作用;

当节点接收到Network PDU时,可以从中获取得到明文的NID域的值,紧接着通过这个NID在Flash中轮询查找是否有其对应的encryption keyprivacy key;如果没有,则该Network PDU被丢弃。所以就算这个时候有其他Mesh网络的数据被接收到也会被丢弃的;更多的详情,请查阅《Mesh Profile》的3.8.6.3.1章节内容。

CTL

该字段只有一个Bit,0表示Access Message且NetMIC为32bits,1表示Control Message且NetMIC为64bits。那么此时不知道有没有读者好奇什么是Control Message、什么是Access Message?反正小编是挺好奇的,但是不在此处开展而是放到Lower Transport Layer中细述。

TTL

由于BLE Mesh当前采用的是泛洪的方式,而不是主流的路由机制。但是该泛洪是可以控制的,那么应如何控制呢?就是利用TTL域的值来控制转发数据的次数。当且仅当TTL>1时,才能中继接受到的数据。

说到TTL就不得不说一下中继(Relay)了,因为TTL域的值只在需要中继时方有效,否则可以忽略它。然则,能不能中继转发是有条件的,下述表示TTL与Relay特性的关联:

SEQ

该字段共24bits长度,表示Network PDU数据包的索引,每一包SEQ都是不同的且从0开始叠加,如果SEQ值要溢出了则就会发起IV更新请求;具体的IV Update Procedure详情,可以参考IV Index更新过程章节的内容

SRC

该字段共16bits的长度,顾名思义就是数据包发起方的源地址且该地址应为单播地址。

DST

SRC一样,该字段域同样为16bits长度,表示数据包要发送的目的地点,其地址可以为单播地址、组地址以及虚拟地址。

Transport PDU

该字段的内容才是Network PDU真正想要传输的,至于能传输的最大长度根据前面提及的CTL值来决定,而相关的内容的细节则将其放在下面的几个章节细述。

NetMIC

同样的,该字段为32bits或者64bits取决于前面提及的CTL值;但是,需要注意的是它只是对DST和Transport PDU的内容进行校验。整个过程以下图所示:

由上述的算法公式可以知道,Network Layer中的Transport PDU和DST是由EncryptionKey加解密的,而EncryptionKey又是由Network Key派生出来的。这里提到了DST和Transport PDU的加解密,就不得不提同样被保护的CTL、TTL、SEQ、SRC等域了,它们的内容则是被模糊化的,而同样用的是由Network Key派生出来的Privacy Key加解密的。至于,EncryptionKey和Privacy Key如何派生出来的,则可以参数下面章节的AID所示;

至此,Network PDU的内容基本讲解完成了;那么,这个时候可能有读者会说:“小编虽然你把每个字段都剖析了,但是串起来还是有点模糊啊”。

别急,我们可以看看接收到Network PDU之后,它们是如何工作的,也许这个时候你 “似乎” 就懂了。具体如下图所示:

从上图 (如何看不清的话就放大看) 我们可以得知,Network PDU想要进来而不是件容易的事啊,经过了层层枷锁,最终才会上传到应用层给用户处理。那么,让我们看看进来之后,都经过了什么考验。

  1. Is NID known?

    我们在NID中已经提及到了,当Node接收到Network PDU时,会通过明文NID在flash中轮询是否有对应的encryption keyprivacy key,如果存在则说明该Network PDU是有效的,因为接下来Node会利用获取得到的encryption keyprivacy key分别来解密network key encryption和obfuscated的加密内容。反之,如果没有轮询一遍发现找不到相应的秘钥,则Network PDU就会被直接抛弃,不会再往上层传递了;

  2. Is SRC valid?

    这个会相对比较简单,它仅仅是判断源地址是不是单播地址,如果是的话就继续下面的验证,否则直接被抛弃;

  3. Already in message cache

    因为我们在IS_NID_KNOWN中就已经获取得到了encryption keyprivacy key,所以CTL、TTL、SEQ、SRC这些域已经去混淆了,这一步用于判断接收到的Network PDU之前是否之前已经接收过,如果是则直接被丢弃;(注意:这里的cache是指的network cache)

  4. An encryption key verifies MIC

    上面小编也说了,到这一步的时候我们已经拿到encryption key了,利用这个秘钥计算出一个Calculated MIC再与接到的Network PDU中的MIC比较;如果是一致的话,那就验证通过并同时将DST、Transport PDU域的内容也解密了,如上图流程图所示;接下来,将数据上抛给上层应用继续处理;否则的话,直接抛弃该网络数据包;

  5. Is DST valid?

    因为步骤4中已经将DST、Transport PDU域的内容解密了,此时就继续判断目标地址是否有效,判断规则如下:

    如果无效的话,同样被抛弃;

  6. Already in replay cache

    到了这一步的时候,除了Transport PDU没有被处理,基本上Network PDU其他域的内容已经被处理完了;该抛弃的抛弃,而留下的都是 “精英”;紧接着判断SEQ + IV INDEX的值 (Replay Attack就是在这里保护了)

    • 如果iv index相等,接收的网络包中的sequence number大于replay cache中同一个源地址的sequence number,则该网络包仍然有效,否则直接丢弃;
    • 如果接收的网络包中的iv index大于replay cache中同一个源地址的iv index,则该网络包仍然有效,否则直接丢弃;

  7. Add to replay cache

    经过上述的6个步骤,基本上这包网络包问题不大了;将当前新的Network PDU的sequence number、iv index、source address保存至replay cache中;

  8. Add to message cache

    同步骤7一样,将将当前新的Network PDU的sequence number、source address保存至message cache中;

  9. Should replay

    而中继的要求,如上述TTL所述;如果满足条件,则将接收到的Network PDU原封不动的重传出去;

至此,整个Network PDU处理流程就完成了;

Lower Transport Layer

Network PDU中的Transport PDU内容,接着由Lower Transport Layer继续进行分割,但是具体的内容则在Network PDU中就已经被安排地明明白白;换句话说就是:Lower Transport PDU传输的内容已经在Network PDU中就已经指明了;而决定的字段则是前面提及的CTL值,即0为Access Message,1为Control Message。

经过上述的内容分析以及文章前面的Mesh_Packet图,我们可以知道Network PDU的Transport PDU又细分为Unsegmented/Segmented Access 或 Control Message,而Access Message的内容则根据AKF的值决定由Device Key还是Application Key加密,然而Control Message的内容则自始自终都是由Network Key加密的。还有一处关键点,小编觉得大家还是要有所了解的,即Access Message的内容可以一直传送到Access Payload层,而Control Message到Upper Transport PDU就停止了;但是,Segment Acknowledgment的Control Message则传至Lower Transport Layer就停止了,也就是说对分段消息的应答是在Lower Transport Layer中处理的!!!

分包与重组

不管是Access Message还是Control Message,都有可能涉及到分包重组的问题,只是前者会出现的比较多点;对于更多的分包与重组细节,我们会专门开篇讲解,这里仅简单讲解否则就会有点本末倒置了。

对于分包的Message,如果目标地址是单播地址,接收完所有的分段Message之后,则需要由对端设备发送应答Message。但是,如果目标地址是虚拟地址或者组地址时,此时则不需要发送应答Message; 我相信有很多读者会问 “对于分段消息的应答信号应该在多长时间内要应答给发送方?总不能“死等”吧?” 没错,肯定不可能“死等”的,而该时长的设置至少为150+50*TTL毫秒。同时,如果在规定的时长内没有收到有效的应答消息,则Lower Transport Layer将重发所有未应答的分段的数据包。这个时候又要读者可能会问“分段的消息不是发完之后,对端设备会发送应答消息吗?为什么会出现重发的情况?”这个其实很好理解,有可能你发送方啪啪啪地发完了所有的分段数据,但是对方不一定啪啪啪全都收到了,很有可能丢了其中的几包,然而上面所说的时长时间一到就必须要给发送方应答你收到了多少包数据,如果全部收到还好,要是收不全这个时候发送方就会重发没有应答的分段数据包了;针对上述的情况,Mesh Spec做出以下几种规定:

Segment Transmission Timer

当Lower Transport Layer PDUs被传输完成时 (注意:是每个分段的消息,但不一定都能被对端设备收到),段传输计时器就应该启动,且其最小值应为200+50*TTL毫秒(Nordic的Mesh SDK默认为500+50xTTL),其中当目标地址是单播地址时,TTL的值就是Network PDU中携带的TTL值,否则TTL为0

Acknowledgment Timer

只要底层传输层接收到分段消息时,且SeqAuth值大于sequence authentication值,且目标地址是单播地址,则将开启应答计时器(第一次是这样,其他时刻看具体情况),其值最小为150+50*TTL毫秒

Incomplete Timer

该计时器跟应答计时器(acknowledgement timer)有点类似,只要底层传输层接收到分段消息,且SeqAuth值大于Sequence Authentication值,则开启未完成计时器(incomplete timer),其值最小为10秒

对于上述Acknowledgment TimerIncomplete Timer中提及到的SeqAuthsequence authentication,前者为当前接收到的分段消息的SeqAuth值,后者为上一次接收到的分段消息的SeqAuth(Nordic MESH SDK默认保存最近收到的4条分段消息)(实质就是通过SeqZero换算成SeqAuth);同样还有一点也是要引起大家注意,Segment Transmission Timer中说的TTL指的是Network Packets中的TTL,而Acknowledgement Timer中说的TTL虽然也属于Network Layer PDU,但是它是专用于Segment Acknowledgment Message的。

Upper Transport Layer

同样的,Upper Transport Layer对Lower Transport Layer的数据进行下一步处理,其通过Device/Application Key解密之后上抛给Access Payload Layer或者将Access Payload Layer下抛的数据进行加密;但是,对于加密则有以下三种情况:

Access Payload Layer

当数据闯关达到这一层时,基本上已经在最顶层了,这也是我们以后用的最多的一层。由于Control Message在Upper Transport Layer就已经停止了 (其中Segment Acknowledgment则在Lower Transport Layer就停止了);如Config Default TTL Get、Config Default TTL Status、Generic OnOff Get等等这些命令,在Access Payload层将统统被解析并展现给开发者,开发者则根据这些命令相应地做出处理。

最后,小编将通过真实的案例(Generic OnOff Set命令的抓包图),将上述的内容全部串连起来。

从上图我们可知,Network PDU的有效内容为Lower Transport PDU,而Lower Transport PDU有效的内容则是PDU即:

B2 34 1B 3C D2 69 23 69 B3 9C

而上述的数据则是由Upper Transport PDU + TransMIC组成,而TransMIC为 “23 69 B3 9C”(Unsegmented的Access Message固定为32bits),故Upper Transport PDU则是:

B2 34 1B 3C D2 69

对比解密后的Access Payload层数据可知,该数据显然是被Application Key加密过的(此时,AKF为1);使用Application Key解密之后,则就是我们最终想要的数据内容了,即Generic OnOff Set命令的相关内容。至此,小编相信现在大家对整个Mesh Packet的内容应该有更深刻地了解了吧,Mesh不同层通过层层加解密最终上抛或者下抛给应用层或到网络层。基于上述的抓包图,不管是从底层往上层还是上层向下层都是可以很好理解的。具体的处理流程如下图所示:

当通过了所有层的检验之后,来到我们顶层的Access Layer;这里继续对解密后的上层传输PDU进行判断,然后上抛到最后的应用层或者在Access Layer被抛弃;

  1. 判断接收到的Message的目标地址是不是单播地址

  2. 如果不是单播地址,紧接着判断是不是固定的组地址 (fixed group address)

    ValuesFixed Group Address Name
    0xFF00–0xFFFBRFU
    0xFFFCall- proxies
    0xFFFDall- friends
    0xFFFEall- relays
    0xFFFFall- nodes

    还要注意的是:如果目标地址是固定的组地址,则只有节点的首要元素 (primary element) 的模型 (model) 能处理该消息

  3. 如果既不是单播地址,也不是固定的组地址,那么再判断是不是订阅地址,通常都是通用的组地址即0xC000~0xFEFF的地址或者虚拟地址;如果1.2.3都不是,那么直接丢弃

  4. 判断匹配的模型是否有匹配的绑定应用秘钥,如果有则交给这个模型的回调函数进行处理,否则直接丢弃

延伸

按理这个时候,该章节已经是接近尾声了;但是,我不知道有没有读者想过这样的问题?如果我的设备掉电复位了或者不小心复位了,下面的场景怎么办?不管读者有没有想过这个问题,马上耐心地跟随小编继续深究下去吧,小编不会再新增内容了 (发现该文章越写越长了,根本停不下来😢

场景1

针对这个场景,主要是message cache和replay cache去检验并拦截已经处理过的Message。而这两个cache均是存在RAM区域的,这也就意味着不管是什么原因导致复位了,那么必然这两个cache肯定是被清空了。而它们实质缓存的内容如下所示:

很遗憾,这个时候这两个cache均未能起到保护的作用。但是,只要它们又接收到一个新的有效的message或者cache中已经有了上一次的内容 (就算这个内容是旧的),那么该保护又恢复了;而失效的情况,只有复位后又刚好第一次就收到了已经处理过的message;

场景2

这个也是很常见的一个情况,这里我就先告诉读者答案:“那就是这个时候,SEQ是不会从0重新开始,又不会继续上一次的值累加,而是比复位前的值大”,至于大多少?那就继续往下看吧。

由上图可知,只要复位之后发的第一包Network PDU的SEQ肯定会比复位前的SEQ大的;复位后的默认最大有效的SEQ值会比SEQ的起始值大8192。至于起始值与最大有效的SEQ值为多少,这里分两种情况:

注意:8192以及64仅仅是Nordic Mesh SDK默认的值,用户完全可以根据自身的情况做出相应的修改。至于其他厂商是什么策略,小编暂时未知,但是最终的结果都是一样的,即“SEQ是不会从0重新开始,也不会继续上一次的值累加,而是比复位前的值大”

除此之外,假如遇到复位之后,当前网络中的IV Index值比之前的大,那么当这个复位后的设备接收到Secure Network Beacon,也会相对应地将IV Index同步为最新的IV Index值,更多的详情请参考后续的IV Index更新过程篇章